//
//  MPBModel.h
//  用于字典转模型的工具类
//  主要功能包括：通过字典转换成对应的模型
//  转模型过程中可以处理一些个性化需求
//  比如：替换字典 key 值，并与指定的属性名对应
//       默认值处理等
//  效率和内存使用都有良好的表现
//
//

/// 基类头文件
#import "MPBModel.h"

/// 支持类头文件
#import "MPBModelSupport.h"


void *propertyStringKey = &propertyStringKey;

/*!
 *
 *  @brief  字典转模型工具类
 *
 */
@implementation MPBModel


/// 从 NSNumber 类型转 NSString 类型
/// 效率很低，尽量避免使用
/// 不能保证服务器端传入的值是基本类型的情况下，属性尽量定义为 NSString
static NSNumberFormatter *_numberFormatter;
static NSLock *lock_Property;


/*!
 *
 *  @brief  类第一次加载时，初始化格式对象
 *
 */
+ (void)load
{
    _numberFormatter = [[NSNumberFormatter alloc] init];
    lock_Property = [[NSLock alloc] init];
}

/*!
 *
 *  @brief  字典转模型协议方法
 *
 *  @param  dict 转模型的字典
 *
 */
- (void)toSelf:(NSDictionary *)dict
{
    if (![dict isKindOfClass:[NSDictionary class]] || dict == nil)
    {
        [self cleanSelf];
        return;
    }
    
    /* 
     ** 需要使用到实例对象的，都在回调函数中处理
     *
     ** 处理过程：
     * 1、   获取所有的属性以及对应的类型（封装的运行时属性、属性编码）（包括继承的父类，直到 SBModel）
     * 1.1、 如果需要可以更换属性名称
     * 2、   取出所有的字典值以及值对应的类型
     * 3、   根据属性的类型赋值
     * 3.1、 如果需要，没有值的情况可以做初始化
     */
    [self selfWithKeyValues:dict];
}

-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{
     id obj = [self init];
    [self toSelf:dict];

    return obj;
}

- (void)cleanSelf
{
    [[self class] enumerateClassProperties:^(MPBModelProperty *property, BOOL *stop)
     {
        if ([property.type.typeClass isSubclassOfClass:[NSArray class]]) /// 数组初始化为 nil
        {
            [property setValue:nil forObject:self];
        }
    }];
}

/*!
 *
 *  @brief  处理属性的核心逻辑
 *
 *  @param  keyValues 字典值
 *
 */
- (void)selfWithKeyValues:(NSDictionary *)keyValues
{
    /// 方法本身用来遍历所有的属性
    /// 方法参数（block 代码块）用来处理每个封装的运行时属性
    [[self class] enumerateClassProperties:^(MPBModelProperty *property, BOOL *stop)
     {
         /// 为内存优化考虑
         @autoreleasepool
         {
             /// 作用1：抓取字典值
             /// 作用2：存最终获取的值
             id value = keyValues;
             
             /// 通过 MPBModelProperty 将运行时属性进行封装，可以处理更多的个性化需求
             /// 此处获取和字典中对应的 key 值，并取出字典对应的值
             /// 此处 key 已经替换成字典中的真实 key
             NSString *key = [property keyFromClass:[self class]];
             value = value[key];
             
             if ([self respondsToSelector:@selector(ignoreProperties)])
             {
                 NSSet *setIgnores = [self ignoreProperties];
                 if ([setIgnores containsObject:key]) return;
             }
             
             if ([self respondsToSelector:@selector(ignoreKeysNotContainInDict)]) {
                 BOOL isIgnoreKeys = [self ignoreKeysNotContainInDict];
                 if (isIgnoreKeys && (!value || value == [NSNull null]))  return;
             }
             
             /// 如果没有值的情况，对特殊需求可以做初始化
             if (!value || value == [NSNull null])
             {
                 /*
                  * 此处可以做默认值处理
                  */
                 
                 if ([property.type.typeClass isSubclassOfClass:[NSString class]]) /// 字符串初始化为 @""
                 {
                     [property setValue:@"" forObject:self];
                 } 
                 else if ([property.type.typeClass isSubclassOfClass:[NSArray class]]) /// 数组初始化为 nil
                 {
                     [property setValue:[@[] mutableCopy] forObject:self];
                 }
                 else if ([property.type.code isEqualToString:@"int"]) /// 整数初始化为最大值
                 {
                     [property setValue:[NSNumber numberWithInteger:INT32_MAX] forObject:self];
                 }
                 
                 return;
             }
             
             
             NSValueTransformer *transformer = [self JSONTransformerForKey:property.name];
             if (transformer != nil) {
                 // Map NSNull -> nil for the transformer, and then back for the
                 // dictionary we're going to insert into.
                 if ([value isEqual:NSNull.null]) value = nil;
                 value = [transformer transformedValue:value] ?: NSNull.null;
             }
             
             /// 通过 MPBModelType 将运行时属性的类型进行封装，可以处理更多的个性化需求以及赋值类型安全判断
             /// 获取模型属性类型
             MPBModelType *type = property.type;
             Class typeClass = type.typeClass;
             
             /// 用于获取数组中存放的对象类型
             Class objectClass;
             BOOL isArrayObjectInitialed = YES;
             
             /// 如果属性对象类型是数组，需要单独处理
             if ([property.type.code isEqualToString:@"NSArray"] || 
                 [property.type.code isEqualToString:@"NSMutableArray"])
             {
                 SEL sel = NSSelectorFromString(property.name);
                 id obj = [self checkPSLeakWarningWithSEL:sel];
                 objectClass = [[obj firstObject] class];
                 [property setValue:[@[] mutableCopy] forObject:self];

                 /// 如果 obj 值为空，就直接按照原值赋值，不影响后面赋值，字典值不会丢失
                 
                 /// 所有没有指定存放类型的数组，全部初始化为可变数组
                 isArrayObjectInitialed = (objectClass != nil);
             }
             
             /// 根据封装的运行时属性类型以及字典值类型赋值
             /// 赋值过程中需要各种判断
             
             if (!type.isFromFoundation && typeClass && ![value isKindOfClass:[MPBModel class]])
             { /// 如果属性是自定义类型，初始化，并赋值
                 id obj = [[typeClass alloc] init];
                 [obj toSelf:value];
                 value = obj;
             }
             else if (objectClass && [objectClass isSubclassOfClass:[MPBModel class]] && [value isKindOfClass:[NSArray class]])
             { /// 如果是数组并且数组已经初始化
                 /// 字典数组 转 模型数组
                 /// 能来到这里，说明数组中的对象类型已经指定
                 value = [objectClass instanceArrayWithKeyValuesArray:value];
             }
             else
                 
                 if (!isArrayObjectInitialed)
             { /// 所有没有指定存放类型的数组，全部初始化为可变数组
                 if ([value isKindOfClass:[NSArray class]])
                 {
                     value = [NSMutableArray arrayWithArray:value];
                 }
                 else
                 {
                     value = [NSMutableArray arrayWithObject:value];
                 }
             }
             else if (typeClass == [NSString class])
             { /// 如果是字符串
                 if ([value isKindOfClass:[NSNumber class]])
                 {
                     /// NSNumber 转 NSString
                     value = [value description];
                 }
                 else if ([value isKindOfClass:[NSURL class]])
                 {
                     /// NSURL 转 NSString
                     value = [value absoluteString];
                 }
             }
             else if ([value isKindOfClass:[NSString class]])
             { /// 以上情况都已经排除，此处通过字典值类型判断，并赋值
                 if (typeClass == [NSURL class])
                 { /// NSString 转 NSURL
                     /// 字符串转码，能够处理中文转义以及特殊字符转义
                     value = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
                                                                                                   (CFStringRef)value,
                                                                                                   (CFStringRef)@"!$&'()*+,-./:;=?@_~%#[]",
                                                                                                   NULL,
                                                                                                   kCFStringEncodingUTF8));
                     value = [NSURL URLWithString:value];
                 }
                 else if (type.isNumberType)
                 { /// NSString -> NSNumber
                     
                     NSString *oldValue = value;
                     value = [_numberFormatter numberFromString:oldValue];
                 }
                 
                 /// BOOL 值不做任何处理，系统会自动将字符串转为 BOOL 类型并赋值
             } 
             else if ([value isKindOfClass:[NSDictionary class]]) 
             {
                 value = [NSMutableDictionary dictionaryWithDictionary:value];
             }
             
             /// 赋值
             /// 当属性是 NSNumber 类型，并且字典值也为 NSNumber 类型时会直接来到这里赋值
             [property setValue:value forObject:self];
         }
     }];
}

/*!
 *
 *  @brief  处理字典数组数据
 *
 *  @param  keyValuesArray 字典数组
 *
 *  @return 返回的模型数组数据
 *
 */
+ (NSMutableArray *)instanceArrayWithKeyValuesArray:(id)keyValuesArray
{
    /// 如果不是数组，原路返回
    if (![keyValuesArray isKindOfClass:[NSArray class]]) return keyValuesArray;
    
    /// 创建数组
    NSMutableArray *modelArray = [NSMutableArray array];
    
    /// 遍历值
    for (NSDictionary *keyValues in keyValuesArray)
    {
        if ([keyValues isKindOfClass:[NSArray class]])
        {
            /// 仍然是数组，继续深入，一般无此情况
            [modelArray addObject:[self instanceArrayWithKeyValuesArray:keyValues]];
        }
        else
        {
            /// 可以直接初始化并赋值
            id obj = [[self alloc] init];
            [obj toSelf:keyValues];
            if (obj) [modelArray addObject:obj];
        }
    }
    
    return modelArray;
}


/*!
 *
 *  @brief  通过回调方式返回类所有的属性，包括所继承的父类的属性
 *          所有嵌套的工具方法都是类方法，效率高
 *
 *  @param  enumeration 回调代码块，用来处理属性并赋值
 *
 */
+ (void)enumerateClassProperties:(void (^)(MPBModelProperty *property, BOOL *stop))enumeration
{
    @autoreleasepool
    {
        /// 获取封装的运行时属性
        NSArray *cachedProperties = [self properties];
        
        /// 遍历成员变量
        BOOL stop = NO;
        for (MPBModelProperty *property in cachedProperties)
        {
            enumeration(property, &stop);
            if (stop) break;
        }
    }
}

/*!
 *
 *  @brief  获取类所有的属性，包括父类的属性
 *
 *  @return 所有属性数组
 *
 */
+ (NSMutableArray *)properties
{
    [lock_Property lock];
    /// 绑定标识
    static const char cachedPropertiesKey = '\0';
    
    /// 通过运行时给封装的运行时属性的 类 添加一个全局缓存
    /// 通过关联对象，获取所有属性
    NSMutableArray *cachedProperties = objc_getAssociatedObject(self, &cachedPropertiesKey);
    
    NSMutableArray *cachedProperStringKey = objc_getAssociatedObject(self, propertyStringKey);

    
    /// 第一次为空，需要初始化
    if (cachedProperties == nil || cachedProperties.count == 0)
    {
        /// 创建一个缓存对象
        cachedProperties = [NSMutableArray array];
        cachedProperStringKey = [NSMutableArray array];
        /// 遍历本类以及父类
        /// block 代码块 作为局部变量传入 enumerateClasses 中执行
        /// 此时，self 类对 block 代码块 具有所有权
        /// 而 block 代码块 的参数 c，可能是 类 本身，为避免循环引用，需要使用弱引用
        [self enumerateClasses:^(__unsafe_unretained Class c, BOOL *stop)
        {
            /// 获得所有的成员变量
            unsigned int outCount = 0;

            objc_property_t *properties = class_copyPropertyList(c, &outCount);
            
            /// 遍历每一个成员变量
            for (unsigned int i = 0; i<outCount; i++)
            {
                MPBModelProperty *property = [MPBModelProperty cachedPropertyWithProperty:properties[i]];
                property.srcClass = c;
                [property setKey:[self propertyKey:property.name] forClass:[self class]];
                [cachedProperties addObject:property];
                [cachedProperStringKey addObject:property.name];
            }
            
            /// 释放内存
            free(properties);
        }];
        
        /// 在此时设置当前这个类为关联对象，下次就不会重复获取类的相关属性
        objc_setAssociatedObject(self,
                                 &cachedPropertiesKey,
                                 cachedProperties,
                                 OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        
        objc_setAssociatedObject(self,
                                 propertyStringKey,
                                 cachedProperStringKey,
                                 OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    [lock_Property unlock];

    return cachedProperties;
}

+ (NSMutableArray *)propertyKeys
{
    [self properties];
    
    NSMutableArray *cachedProperStringKey = objc_getAssociatedObject(self, propertyStringKey);
    
    return cachedProperStringKey;
}

/*!
 *
 *  @brief  直接执行 selector 方法出现警告的处理
 *
 *  @param  selector 需要执行的方法
 *
 *  @return 返回执行方法返回的对象
 *
 */
- (id)checkPSLeakWarningWithSEL:(SEL)selector
{
    
    /// 获取方法的实现，直接让对象执行方法，如果方法名不正确，会抛出运行时错误
    IMP imp = [self methodForSelector:selector];
    id (*func)(id, SEL) = (void *)imp;
    
    return func(self, selector);
}

/*!
 *
 *  @brief  获取运行时属性的 key，与字典中的 key 对应
 *
 *  @param  propertyName 属性定义名称
 *
 *  @return 运行时属性的 key
 *
 */
+ (NSString *)propertyKey:(NSString *)propertyName
{
//    NSAssert(propertyName != nil, @"propertyName 不能为空");
    if (propertyName == nil) return nil;
    
    NSString *key = nil;
    
    /// 查看有没有需要替换的key
    if ([self respondsToSelector:@selector(JSONKeyPathsByPropertyKey)])
    {
        key = [self JSONKeyPathsByPropertyKey][propertyName];
    }
    
    /// 用属性名作为key
    if (!key) key = propertyName;
    
    return key;
}

/*!
 *
 *  @brief  在此遍历出所有关联类
 *
 *  @param  enumeration 代码块中遍历每个类的所有属性
 *
 */
+ (void)enumerateClasses:(void (^)(__unsafe_unretained Class c, BOOL *stop))enumeration
{
    /// block 为空就直接返回
    if (enumeration == nil) return;
    
    /// 初始化停止标记
    BOOL stop = NO;
    
    /// 当前遍历的类
    Class c = self;
    
    /// 开始遍历每一个类
    while (c && !stop)
    {
        /// 执行操作
        enumeration(c, &stop);
        
        /// 获得父类
        c = class_getSuperclass(c);
        
        /// 遍历结束条件，直到 MPBModel 基类
        if ([NSStringFromClass(c) isEqualToString:@"MPBModel"]) break;
    }
}

SEL MTLSelectorWithKeyPattern(NSString *key, const char *suffix)
{
    NSUInteger keyLength = [key maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding];
    NSUInteger suffixLength = strlen(suffix);
    
    char selector[keyLength + suffixLength + 1];
    
    BOOL success = [key getBytes:selector maxLength:keyLength usedLength:&keyLength encoding:NSUTF8StringEncoding options:0 range:NSMakeRange(0, key.length) remainingRange:NULL];
    if (!success) return NULL;
    
    memcpy(selector + keyLength, suffix, suffixLength);
    selector[keyLength + suffixLength] = '\0';
    
    return sel_registerName(selector);
}

- (NSValueTransformer *)JSONTransformerForKey:(NSString *)key
{
    NSParameterAssert(key != nil);
    
    SEL selector = MTLSelectorWithKeyPattern(key, "JSONTransformer");
    if ([[self class] respondsToSelector:selector]) {
        IMP imp = [[self class] methodForSelector:selector];
        NSValueTransformer * (*function)(id, SEL) = (__typeof__(function))imp;
        NSValueTransformer *transformer = function([self class], selector);
        return transformer;
    }
    
    if ([[self class] respondsToSelector:@selector(JSONTransformerForKey:)]) {
        return [[self class] JSONTransformerForKey:key];
    }
    
    return nil;
}

- (NSDictionary *)dictionaryValue
{
    return [self dictionaryWithValuesForKeys:[self.class propertyKeys]];
}



#pragma mark NSCopying

- (instancetype)copyWithZone:(NSZone *)zone
{
    if ([[self class] respondsToSelector:@selector(JSONKeyPathsByPropertyKey)])
    {
        NSMutableDictionary *mdic = [self.dictionaryValue mutableCopy];
        
        NSDictionary *dicReplace = [[self class] JSONKeyPathsByPropertyKey];
        for (NSString *keyLocal in dicReplace)
        {
            id keyNet = [dicReplace valueForKey:keyLocal];
            id value = [mdic valueForKey:keyLocal];
            [mdic setValue:value forKey:keyNet];
            [mdic removeObjectForKey:keyLocal];
        }
        
        return [[self.class allocWithZone:zone] initWithDictionary:mdic error:NULL];
    }
    
    return [[self.class allocWithZone:zone] initWithDictionary:self.dictionaryValue error:NULL];
}

#pragma mark NSObject

- (NSString *)description
{
    return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.dictionaryValue];
}

- (NSUInteger)hash
{
    NSUInteger value = 0;
    
    for (NSString *key in self.class.propertyKeys)
    {
        value ^= [[self valueForKey:key] hash];
    }
    
    return value;
}

- (BOOL)isEqual:(MPBModel *)model
{
    if (self == model) return YES;
    if (![model isMemberOfClass:self.class]) return NO;
    
    for (NSString *key in self.class.propertyKeys)
    {
        id selfValue = [self valueForKey:key];
        id modelValue = [model valueForKey:key];
        
        BOOL valuesEqual = ((selfValue == nil && modelValue == nil) || [selfValue isEqual:modelValue]);
        if (!valuesEqual)
        {
            return NO;
        }
    }
    
    return YES;
}




@end
